/*  
===============================================================================
Chili is the jQuery code highlighter plugin
...............................................................................
                                               Copyright 2007 / Andrea Ercolino
-------------------------------------------------------------------------------
LICENSE: http://www.opensource.org/licenses/mit-license.php
WEBSITE: http://noteslog.com/chili/
===============================================================================

===============================================================================
Metaobjects is the jQuery metadata plugin on steroids
...............................................................................
                                               Copyright 2007 / Andrea Ercolino
-------------------------------------------------------------------------------
LICENSE: http://www.opensource.org/licenses/mit-license.php
WEBSITE: http://noteslog.com/metaobjects/
===============================================================================
*/

//-----------------------------------------------------------------------------
( function($) {

ChiliBook = { //implied global

      version:            "1.8b" // 2007-05-26

    , automatic:          true
    , automaticSelector:  "code"

    , codeLanguage:       function( el ) {
        var recipeName = $( el ).attr( "class" );
        return recipeName ? recipeName : '';
    }

    , metadataSelector:   "object.chili"             // use an empty string for not executing

    , recipeLoading:      true
    , recipeFolder:       "" // used like: recipeFolder + recipeName + '.js'
    , stylesheetLoading:  true
    , stylesheetFolder:   "" // used like: stylesheetFolder + recipeName + '.css'

    , defaultReplacement: '<span class="$0">$$</span>'

    , replaceSpace:       "&#160;"                   // use an empty string for not replacing
    , replaceTab:         "&#160;&#160;&#160;&#160;" // use an empty string for not replacing
    , replaceNewLine:     "&#160;<br/>"              // use an empty string for not replacing

    , recipes:            {} //repository
    , queue:              {} //register

    //fix for IE: copy of PREformatted text strips off all html, losing newlines
    , preFixCopy:         document.selection && document.selection.createRange
    , preContent:         ""
    , preElement:         null
};


$.metaobjects = function( options ) { 
 
    options = $.extend( { 
          context:  document 
        , clean:    true 
        , selector: 'object.metaobject' 
    }, options ); 
 
    function jsValue( value ) { 
        eval( 'value = ' + value + ";" ); 
        return value; 
    } 
 
    return $( options.selector, options.context ) 
    .each( function() { 
 
        var settings = { target: this.parentNode }; 
        $( '> param[@name=metaparam]', this ) 
        .each( function() {  
            $.extend( settings, jsValue( this.value ) ); 
        } ); 
 
        $( '> param', this ) 
        .not( '[@name=metaparam]' ) 
        .each( function() { 
            var name = this.name, value = jsValue( this.value ); 
            $( settings.target ) 
            .each( function() { 
                this[ name ] = value; 
            } ); 
        } ); 
 
        if( options.clean ) { 
            $( this ).remove(); 
        } 
    } ); 
}; 

$.fn.chili = function( options ) {
    var book = $.extend( {}, ChiliBook, options || {} );

    function cook( ingredients, recipe ) {

        function prepareStep( stepName, step ) {
            var exp = ( typeof step.exp == "string" ) ? step.exp : step.exp.source;
            steps.push( {
                stepName: stepName
                , exp: "(" + exp + ")"
                , length: 1                         // add 1 to account for the newly added parentheses
                    + (exp                          // count number of submatches in here
                        .replace( /\\./g, "%" )     // disable any escaped character
                        .replace( /\[.*?\]/g, "%" ) // disable any character class
                        .match( /\((?!\?)/g )       // match any open parenthesis, not followed by a ?
                    || []                           // make sure it is an empty array if there are no matches
                    ).length                        // get the number of matches
                , replacement: (step.replacement) ? step.replacement : book.defaultReplacement 
            } );
        } // function prepareStep( stepName, step )
    
        function knowHow() {
            var prevLength = 0;
            var exps = new Array;
            for (var i = 0; i < steps.length; i++) {
                var exp = steps[ i ].exp;
                // adjust backreferences
                exp = exp.replace( /\\\\|\\(\d+)/g, function( m, aNum ) {
                    return !aNum ? m : "\\" + ( prevLength + 1 + parseInt( aNum, 10 ) );
                } );
                exps.push( exp );
                prevLength += steps[ i ].length;
            }
            var source = exps.join( "|" );
            return new RegExp( source, (recipe.ignoreCase) ? "gi" : "g" );
        } // function knowHow()

        function escapeHTML( str ) {
            return str.replace( /&/g, "&amp;" ).replace( /</g, "&lt;" );
        } // function escapeHTML( str )

        function replaceSpaces( str ) {
            return str.replace( / +/g, function( spaces ) {
                return spaces.replace( / /g, replaceSpace );
            } );
        } // function replaceSpaces( str )

        function filter( str ) {
            str = escapeHTML( str );
            if( replaceSpace ) {
                str = replaceSpaces( str );
            }
            return str;
        } // function filter( str )

        function chef( matched ) {
            var i = 0;  // iterate steps
            var j = 1;    // iterate chef's arguments
            var step;
            while( step = steps[ i++ ] ) {
                var aux = arguments; // this unmasks chef's arguments inside the next function
                if( aux[ j ] ) {
                    var pattern = /(\\\$)|(?:\$\$)|(?:\$(\d+))/g;
                    var replacement = step.replacement
                        .replace( pattern, function( m, escaped, K ) {
                            var bit = '';
                            if( escaped ) {       /* \$ */ 
                                return "$";
                            }
                            else if( !K ) {       /* $$ */ 
                                return filter( aux[ j ] );
                            }
                            else if( K == "0" ) { /* $0 */ 
                                return step.stepName;
                            }
                            else {                /* $K */
                                return filter( aux[ j + parseInt( K, 10 ) ] );
                            }
                        } );

                    var offset = arguments[ arguments.length - 2 ];
                    var input = arguments[ arguments.length - 1 ];
                    var unmatched = input.substring( lastIndex, offset );
                    lastIndex = offset + matched.length; // lastIndex for the next call to chef

                    perfect += filter( unmatched ) + replacement; // use perfect for all the replacing
                    return replacement;
                } 
                else {
                    j+= step.length;
                }
            }
        } // function chef( matched )

        var replaceSpace = book.replaceSpace;
        var steps = new Array;
        for( var stepName in recipe.steps ) {
            prepareStep( stepName, recipe.steps[ stepName ] );
        }

        var perfect = ""; //replace doesn't provide a handle to the ongoing partially replaced string
        var lastIndex = 0; //regexp.lastIndex is available after a string.match, but not in a string.replace
        ingredients.replace( knowHow(), chef );
        var lastUnmatched = ingredients.substring( lastIndex, ingredients.length );
        perfect += filter( lastUnmatched );

        return perfect;

    } // function cook( ingredients, recipe )

    function checkCSS( stylesheetPath ) {
        if( ! book.queue[ stylesheetPath ] ) {
            var e = document.createElement( "link" );
            e.rel = "stylesheet";
            e.type = "text/css";
            e.href = stylesheetPath;
            document.getElementsByTagName( "head" )[0].appendChild( e );

            book.queue[ stylesheetPath ] = true;
        }
    } // function checkCSS( recipeName )

    function makeDish( el, recipePath ) {
        var recipe = book.recipes[ recipePath ];
        if( ! recipe ) {
            return;
        }
        var chunks = el && el.childNodes && el.childNodes.length;
        if( chunks && chunks > 1 ) {
            return;
        }
        var ingredients = el.childNodes[0] && el.childNodes[0].data;
        if( ! ingredients ) {
            return;
        }
        // hack for IE: \r is used instead of \n
        ingredients = ingredients.replace(/\r\n?/g, "\n");

        var dish = cook( ingredients, recipe ); // all happens here
    
        if( book.replaceTab ) {
            dish = dish.replace( /\t/g, book.replaceTab );
        }
        if( book.replaceNewLine ) {
            dish = dish.replace( /\n/g, book.replaceNewLine );
        }

        $( el ).html( dish );
        if( ChiliBook.preFixCopy ) {
            $( el )
            .parents()
            .filter( "pre" )
            .bind( "mousedown", function() {
                ChiliBook.preElement = this;
            } )
            .bind( "mouseup", function() {
                if( ChiliBook.preElement == this ) {
                    ChiliBook.preContent = document.selection.createRange().htmlText;
                }
            } )
            ;
        }
    } // function makeDish( el )

    function getPath( recipeName, options ) {
        var settingsDef = {
              recipeFolder:     book.recipeFolder
            , recipeFile:       recipeName + ".js"
            , stylesheetFolder: book.stylesheetFolder
            , stylesheetFile:   recipeName + ".css"
        };
        var settings;
        if( options && typeof options == "object" ) {
            settings = $.extend( settingsDef, options );
        }
        else {
            settings = settingsDef;
        }
        return {
              recipe    : settings.recipeFolder     + settings.recipeFile
            , stylesheet: settings.stylesheetFolder + settings.stylesheetFile
        };
    } //function getPath( recipeName, options )

//-----------------------------------------------------------------------------
// initializations
    if( book.metadataSelector ) {
        $.metaobjects( { context: this, selector: book.metadataSelector } );
    }

//-----------------------------------------------------------------------------
// the coloring starts here
    this
    .each( function() {
        var el = this;
        var recipeName = book.codeLanguage( el );
        if( '' != recipeName ) {
            var path = getPath( recipeName, el.chili );
            if( book.recipeLoading || el.chili ) {
                /* dynamic setups come here */
                if( ! book.queue[ path.recipe ] ) {
                    /* this is a new recipe to download */
                    try {
                        book.queue[ path.recipe ] = [ el ];
                        $.getJSON( path.recipe, function( recipeLoaded ) {
                            recipeLoaded.path = path.recipe;
                            book.recipes[ path.recipe ] = recipeLoaded;
                            if( book.stylesheetLoading ) {
                                checkCSS( path.stylesheet );
                            }
                            var q = book.queue[ path.recipe ];
                            for( var i = 0, iTop = q.length; i < iTop; i++ ) {
                                makeDish( q[ i ], path.recipe );
                            }
                        } );
                    }
                    catch( recipeNotAvailable ) {
                        alert( "the recipe for '" + recipeName + "' was not found in '" + path.recipe + "'" );
                    }
                }
                else {
                    /* not a new recipe, so just enqueue this element */
                    book.queue[ path.recipe ].push( el );
                }
                /* a recipe could have been already downloaded */
                makeDish( el, path.recipe ); 
            }
            else {
                /* static setups come here */
                makeDish( el, path.recipe );
            }
        }
    } );

    return this;
//-----------------------------------------------------------------------------
};

//main
$( function() {

    if( ChiliBook.automatic ) {
        if( ChiliBook.elementPath ) {
            //preserve backward compatibility
            ChiliBook.automaticSelector = ChiliBook.elementPath;
            if( ChiliBook.elementClass ) {
                ChiliBook.codeLanguage = function ( el ) {
                    var selectClass = new RegExp( "\\b" + ChiliBook.elementClass + "\\b", "gi" );
                    var elClass = $( el ).attr( "class" );
                    if( ! elClass ) { 
                        return ''; 
                    }
                    var recipeName = $.trim( elClass.replace( selectClass, "" ) );
                    return recipeName;
                };
            }
        }

        $( ChiliBook.automaticSelector ).chili();
    }

    if( ChiliBook.preFixCopy ) {
        function preformatted( text ) {
            if( '' == text ) { 
                return ""; 
            }
            do { 
                var newline_flag = (new Date()).valueOf(); 
            }
            while( text.indexOf( newline_flag ) > -1 );
            text = text.replace( /\<br[^>]*?\>/ig, newline_flag );
            var el = document.createElement( '<pre>' );
            el.innerHTML = text;
            text = el.innerText.replace( new RegExp( newline_flag, "g" ), '\r\n' );
            return text;
        }

        $( "body" )
        .bind( "copy", function() {
            if( '' != ChiliBook.preContent ) {
                window.clipboardData.setData( 'Text', preformatted( ChiliBook.preContent ) );
                event.returnValue = false;
            }
        } )
        .bind( "mousedown", function() {
            ChiliBook.preContent = "";
        } )
        .bind( "mouseup", function() {
            ChiliBook.preElement = null;
        } )
        ;
    }

} );

} ) ( jQuery );